#include "CudaEpiModel.h"
#include "CudaParam.h"
#include <thrust/binary_search.h>
#include <thrust/sort.h>

bool compareModel(EpiModel& compare_model){
	copyPerson(false);
	for(int i = 0; i < compare_model.nNumPerson; i++){
		if(fabs(compare_model.pvec[i].prs - model->pvec[i].prs) > 1e-5){ 
			cout << "at :" << i <<  " compare: " << compare_model.pvec[i].prs  << " orignial: " << model->pvec[i].prs << endl;
			return false;
		}
	}
	return true;
}

void cudaInitialize(EpiModel& input_model){
	model = &input_model;
    // transfer constant  information to constant memory
    cudaMemcpyToSymbol(dvload,model->vload,VLOADNSUB*VLOADNDAY*sizeof(double)); // vload
    cudaMemcpyToSymbol(dseasonality,model->seasonality,MAXRUNLENGTH*sizeof(double)); // seasonality
    cudaMemcpyToSymbol(dfVaccineEfficacyByAge,model->fVaccineEfficacyByAge,TAG*sizeof(double)); // fVaccineEfficacyByAge
    cudaMemcpyToSymbol(dVaccineData,model->VaccineData,NUMVACCINES*sizeof(vaccinedatastruct)); // vaccineData
	cudaMemcpyToSymbol(dcpfc,cpfc,TAG*sizeof(double));
	cudaMemcpyToSymbol(dcpfa,cpfa,TAG*sizeof(double));
	cudaMemcpyToSymbol(dcphcc,cphcc,TAG*sizeof(double));
	cudaMemcpyToSymbol(dcphca,cphca,TAG*sizeof(double));
	cudaMemcpyToSymbol(dwithdrawcdf32,model->withdrawcdf32,3*WITHDRAWDAYS*sizeof(unsigned int)); // withdrawcef32
	cudaMemcpyToSymbol(dincubationcdf32,incubationcdf32,3*sizeof(unsigned int)); // incubationcdf32
	cudaMemcpyToSymbol(dcps,cps,10*sizeof(double)); // cps
	cudaMemcpyToSymbol(d_travel_length_cdf,travel_length_cdf,12*sizeof(double)); // travel_length_cdf
	cudaMemcpyToSymbol(d_travel_pr,travel_pr,TAG*sizeof(double)); // travel_pr
	cudaMemcpyToSymbol(dnSchoolOpeningDays,model->nSchoolOpeningDays,56*sizeof(int));
	cudaMemcpyToSymbol(dnVaccinePriorities2,model->nVaccinePriorities2,PRIORITY_LAST*sizeof(unsigned int));
	
	// copy person vector
	ppl_vec = new dPersonVec(model->nNumPerson);
	copyPerson(true);

	// copy community vector
	comm_vec = new dCommunityVec(model->nNumCommunities);
	copyCommunity(true);

	// copy tract vector
	tract_vec = new dTractVec(model->nNumTractsTotal);
	copyTract(true);

	rand_vec = new dRandVec(model->nNumCommunities);
	// srand(time(NULL)); 
	srand(2);
	hRandVec rand_temp;
	for(int i = 0; i < model->nNumCommunities; i++){rand_temp.push_back(RandGen(rand(),rand(),rand(),rand()));}
	*rand_vec = rand_temp;

	// initialize community counter and construct comm_woker_vec and comm_visitior_vec
	unsigned int worker_size = 0; 
	unsigned int visitor_size = 0;
	hCommWorkerVec comm_worker_temp,comm_worker_counter_temp;
	hCommVisitorVec comm_visitor_temp,comm_visitor_counter_temp;

	dPerson dummy_ppl;
	for(unsigned int i = 0; i < model->nNumCommunities; i++){
		comm_worker_counter_temp.push_back(dCommWorker(i,0));
		comm_visitor_counter_temp.push_back(dCommVisitor(i,dummy_ppl));

		for(unsigned int j = 0; j < model->commvec[i].workers.size(); j++){
			comm_worker_temp.push_back(dCommWorker(i,model->commvec[i].workers[j]));
		}
		worker_size += model->commvec[i].workers.size();	

		for(list< Person >::iterator it = model->commvec[i].visitors.begin();
		    it != model->commvec[i].visitors.end(); ++it){
			Person &p = *it;
			comm_visitor_temp.push_back(dCommVisitor(i,dPerson(p)));
			visitor_size++;
		}	
	}

	comm_worker_counter = new dCommWorkerVec(model->nNumCommunities);
	comm_visitor_counter = new dCommVisitorVec(model->nNumCommunities);
	*comm_worker_counter = comm_worker_counter_temp;
	*comm_visitor_counter = comm_visitor_counter_temp;

	comm_worker_vec = new dCommWorkerVec(worker_size);
	comm_visitor_vec = new dCommVisitorVec(visitor_size);
	*comm_worker_vec = comm_worker_temp;
	*comm_visitor_vec = comm_visitor_temp;

	// find the start index and end index of worker and visitor for each community
	comm_worker_start_vec = new dIdxVec(model->nNumCommunities);
	comm_worker_end_vec = new dIdxVec(model->nNumCommunities);
	// since visitor is initially empty, so no need to setup
	comm_visitor_start_vec = new dIdxVec(model->nNumCommunities);
	comm_visitor_end_vec = new dIdxVec(model->nNumCommunities);

	
	thrust::lower_bound(
		comm_worker_vec->begin(),comm_worker_vec->end(),
		comm_worker_counter->begin(),comm_worker_counter->end(),
		comm_worker_start_vec->begin());

	thrust::upper_bound(
		comm_worker_vec->begin(),comm_worker_vec->end(),
		comm_worker_counter->begin(),comm_worker_counter->end(),
		comm_worker_end_vec->begin());


	cudaMalloc((void**)&nNumTAPDone,sizeof(unsigned int));
	cudaMalloc((void**)&nNumWantAV,sizeof(unsigned int));
	cudaMalloc((void**)&nNumWantVaccine,sizeof(unsigned int));
	cudaMalloc((void**)&nNumAntiviralsUsed,sizeof(unsigned int));
	
	cudaMemcpy(nNumTAPDone,&model->nNumTAPDone,sizeof(unsigned int),cudaMemcpyHostToDevice);
	cudaMemcpy(nNumWantAV,&model->nNumWantAV,sizeof(unsigned int),cudaMemcpyHostToDevice);
	cudaMemcpy(nNumWantVaccine,&model->nNumWantVaccine,sizeof(unsigned int),cudaMemcpyHostToDevice);
	cudaMemcpy(nNumAntiviralsUsed,&model->nNumAntiviralsUsed,sizeof(unsigned int),cudaMemcpyHostToDevice);
}

// true: transfer person vector from host to device
// false: transfer person vector from device to host
void copyPerson(bool host_to_device){
	hPersonVec temp;
	if(host_to_device){

	vector< Person >::iterator pend = model->pvec.end();
	for(vector< Person >::iterator it = model->pvec.begin(); it != pend; it++){
		Person &p = *it;
		dPerson new_ppl(p);
		temp.push_back(new_ppl);
	}
	*ppl_vec = temp;
	
	}else{
	
	temp.resize(model->nNumPerson);
	temp = *ppl_vec;
	for(int i = 0; i < model->nNumPerson; i++){ temp[i].copyToPerson(model->pvec[i]); }
	}
}

// true: transfer community vector from host to device
// false: transfer community vector from device to host
void copyCommunity(bool host_to_device){
	hCommunityVec temp;
	if(host_to_device){

	vector< Community >::iterator cend = model->commvec.end();
	for(vector< Community >::iterator it = model->commvec.begin(); it != cend; it++){
		Community &comm = *it;
		dCommunity new_comm(comm);
		temp.push_back(new_comm);
	}	
	*comm_vec = temp;

	}else{
	
	temp.resize(model->nNumCommunities);
	temp = *comm_vec;
	for(int i = 0; i < model->nNumCommunities; i++){ temp[i].copyToCommunity(model->commvec[i]); }
	}
}

// true: transfer tract vector from host to device
// false: transfer tract vector from device to host
void copyTract(bool host_to_device){
	hTractVec temp;
	if(host_to_device){

	vector< Tract >::iterator cend = model->tractvec.end();
	for(vector< Tract >::iterator it = model->tractvec.begin(); it != cend; it++){
		Tract &t = *it;
		dTract new_t(t);
		temp.push_back(new_t);
	}	
	*tract_vec = temp;

	}else{
	
	temp.resize(model->nNumTractsTotal);
	temp = *tract_vec;
	for(int i = 0; i < model->nNumTractsTotal; i++){ temp[i].copyToTract(model->tractvec[i]); }
	}
}

void clean(){
	delete ppl_vec;
	delete comm_vec;
	delete comm_worker_vec; 
	delete comm_worker_start_vec; 
	delete comm_worker_end_vec;  

	delete comm_visitor_vec; 
	delete comm_visitor_start_vec; 
	delete comm_visitor_end_vec; 

	delete comm_worker_counter;
	delete comm_visitor_counter;
	delete rand_vec;
}